<template>
  <div class="draw-view">
    <template v-if="src">
      <div class="img-view">
        <img :src="src" @load="photoLoad"/>
        <!-- 预览相框 -->
        <!-- <img :src="framePreviewSrc"> -->
      </div>
      <template v-if="loaded">
        <FrameItem resizeEnable :editEnable="editEnable" :configs="currentFrameConfigs" @menu-click="frameMenuClick" />
        <div class="tool-bar" v-show="!editEnable">
          <el-button @click="onMenuClick('throw-away')" class="btn" type="default" size="default" icon="Delete">丢掉</el-button>
          <el-button @click="onMenuClick('select-frame')" class="btn" type="primary" size="default" icon="Tickets">换风格</el-button>
          <el-button @click="onMenuClick('edit-frame')" class="btn" type="success" size="default" icon="Edit">编辑</el-button>
          <el-button @click="toExport" class="btn" type="danger" size="default" icon="FolderAdd">导出</el-button>
        </div>
      </template>
    </template>
    <el-drawer
      class="frameDrawer"
      v-model="frameDrawerShow"
      title="选择风格"
      direction="ltr"
      :size="820"
      :close-on-click-modal="false"
      @opened="frameDrawerOpened"
      @closed="frameDrawerClosed"
    >
      <FrameSelect v-if="frameDrawerShow" :logo="logoSrc" :exifData="exifData" @success="frameSelectSuccess" />
    </el-drawer>
    <div class="export-progress-modal" v-if="progressShow">
      <div class="progress-main">
        <el-progress :percentage="percentage" :stroke-width="15" :duration="30" striped-flow striped />
        <div class="desc">图像合成中...</div>
      </div>
    </div>
  </div>
</template>


<script setup>
import { ref, onMounted, defineProps, defineEmits, onBeforeUnmount, computed } from 'vue'
import FrameItem from './FrameItem.vue'
import FrameSelect from './FrameSelect.vue'
const fs = require('fs')
const remote = require("@electron/remote")   //引入remote模块
const sharp = require("sharp")


const $emit = defineEmits(['success', 'menu-click'])

const props = defineProps({
  // 绘制配置
  drawData: {
    type: Object,
    required: true,
    default() {
      return {
        file: null, // 照片文件
        frameConfigs: [], // 边框配置信息
        frameProportion: 11 // 相框宽高比
      }
    }
  }
})

let photo = new Image() // 照片
let canvas = document.createElement('canvas') // 导出照片+相框的canvas
let logoImg = new Image() // 图标
let context = canvas.getContext("2d")
const editEnable = ref(false)
let saveFileName = ''
let saveFileData = null
const dpr = Math.ceil(Math.max(window.devicePixelRatio, 2)) // 设备像素比
const src = ref(null)
const frameMaking = ref(true)
const loaded = ref(false)
const frameDrawerShow = ref(false)
const currentFrameConfigs = ref(props.drawData.frameConfigs)
const frameProportion = ref(props.drawData.frameProportion)
const logoSrc = ref('')
const percentage = ref(0)
let frameBlob = null
const progressShow = ref(false)
// 相框信息
let frameRect = {
  width: 0,
  height: 0,
}
// 照片尺寸
let photoSize = {
  width: 0,
  height: 0,
}
// const framePreviewSrc = ref('')

// 根据编辑相框内容的配置更新风格列表的exif数据内容
const exifData = computed(() => {
  const configs = currentFrameConfigs.value
  const data = JSON.parse(window.localStorage.getItem('originalExif'))
  configs.forEach(item => {
    if (item.prop in data) {
      data[item.prop] = item.content
    }
  })
  return data
})



// 获取文字宽度
const getTextWidth = (context, options) => {
  const { height } = frameRect
  const { fontSize, fontFamily, fontWeight, fontVariant, fontStyle } = options.font
  context.save()
  context.font = `${fontStyle || 'normal'}  ${fontVariant || 'normal'}  ${fontWeight || 'normal'} ${fontSize || (height * 0.22)}px ${fontFamily || 'arial'}`
  context.fillStyle = '#000000'
  context.textAlign = 'left'
  context.textBaseline = 'top'
  const measureText = context.measureText(options.content)
  context.restore()
  return measureText.width
}

// 绘制文字
const drawText = (context, options) => {
  const { width, height } = frameRect
  const { fontSize, fontFamily, fontWeight, fontVariant, fontStyle, fontColor } = options.font
  context.save()
  context.font = `${fontStyle || 'normal'}  ${fontVariant || 'normal'}  ${fontWeight || 'normal'} ${fontSize || (height * 0.22)}px ${fontFamily || 'arial'}`
  context.fillStyle = fontColor || '#000000'
  context.textAlign = options.textAlign || 'left'
  context.textBaseline = options.textBaseline || 'top'
  let measureText = context.measureText(options.content)
  if (options.textAlign === 'right') {
    context.fillText(options.content, width + options.position.x, options.position.y)
  } else {
    context.fillText(options.content, options.position.x, options.position.y)
  }

  context.restore()
  return measureText
}

// 绘制图片
const drawImage = (context, options) => {
  context.save()
  context.drawImage(options.img, options.x, options.y, options.width, options.height)
  context.restore()
}

// 绘制线
const drawLine = (context, options) => {
  context.save()
  context.fillStyle = options.color || "#c6c6c6"
  context.fillRect(options.x, options.y, options.width, options.height)
  context.restore()
}

// 获取水印所在位置的内容配置项
const getAlignItem = (alignValue, frameConfigs) => {
  return frameConfigs.find(item => item.align === alignValue)
}


const drawFrame = (context) => {
  const frameConfigs = currentFrameConfigs.value
  const aligns = frameConfigs.map(item => item.align)
  // 水平内间距
  const padding = frameRect.height * 0.25
  const isVertical = !!getAlignItem('center-top', frameConfigs) // 是否垂直水平居中排版
  const lineHeight = frameRect.height * 0.55 // 分割线高度
  let logoHeight = logoImg.naturalWidth / logoImg.naturalHeight < 2 ? lineHeight * 0.9 : frameRect.height * 0.3
  if (isVertical) {
    logoHeight = logoImg.naturalWidth / logoImg.naturalHeight < 2 ? frameRect.height * 0.3 : frameRect.height * 0.23
  }
  let logoWidth = logoHeight * (logoImg.naturalWidth / logoImg.naturalHeight)
  const lineXMargin = frameRect.width * 0.02 // 分割线水平间距
  const lineWidth = Math.max(frameRect.width * 0.001, 2) // 分割线宽度
  const marginBottom = aligns.length > 2 ? frameRect.height * 0.08 : frameRect.height * 0.12 // 内容底部间距

  // 获取右侧文字宽度: 最右侧两行文字统一左对齐
  let rightTopTextWidth = 0
  let rightBottomTextWidth = 0
  if (aligns.includes('right-top') && aligns.includes('right-bottom')) {
    const rightTopItem = getAlignItem('right-top', frameConfigs)
    const rightBottomItem = getAlignItem('right-bottom', frameConfigs)
    // 右侧文字通用配置
    const rightTopTextBase = {
      font: {
        fontSize: frameRect.height * rightTopItem.style.fontRatio,
        fontWeight: rightTopItem.style.fontWeight,
      },
      content: rightTopItem.content
    }
    const rightBottomTextBase = {
      font: {
        fontSize: frameRect.height * rightBottomItem.style.fontRatio,
        fontWeight: rightBottomItem.style.fontWeight,
      },
      content: rightBottomItem.content
    }
    rightTopTextWidth = getTextWidth(context, rightTopTextBase)
    rightBottomTextWidth = getTextWidth(context, rightBottomTextBase)
  }

  frameConfigs.forEach(item => {
    if (item.prop !== 'logo') {
      let {fontRatio} = item.style
      item.height = frameRect.height * fontRatio
    } else {
      item.height = logoHeight
    }
  })

  // 记录每列文字总高度 (用于处理计算垂直居中)
  const unfound = {height: 0}
  const leftTopItem = getAlignItem('left-top', frameConfigs) || unfound
  const leftBottomItem = getAlignItem('left-bottom', frameConfigs) || unfound
  const rightTopItem = getAlignItem('right-top', frameConfigs) || unfound
  const rightBottomItem = getAlignItem('right-bottom', frameConfigs) || unfound
  const offsetLeftTopItem = getAlignItem('offset-left-top', frameConfigs) || unfound
  const offseLeftBottomItem = getAlignItem('offset-left-bottom', frameConfigs) || unfound
  const centerTopItem = getAlignItem('center-top', frameConfigs) || unfound
  const centerMiddleItem = getAlignItem('center-middle', frameConfigs) || unfound
  const centerBottomItem = getAlignItem('center-bottom', frameConfigs) || unfound
  const colRect = {
    leftContentHeight: leftTopItem.height + marginBottom + leftBottomItem.height,
    rightContentHeight: rightTopItem.height + marginBottom + rightBottomItem.height,
    offsetLeftContentHeight: offsetLeftTopItem.height + marginBottom + offseLeftBottomItem.height,
    centerMiddleContentHeight: centerTopItem.height + marginBottom + (centerMiddleItem.height ? (centerMiddleItem.height + marginBottom) : 0) + centerBottomItem.height
  }
  
  frameConfigs.forEach(item => {
    if (item.prop !== 'logo') {
      let {fontWeight, fontColor, fontRatio} = item.style
      let x = 0, y = 0, textAlign = 'left', textWidth = getTextWidth(context, {
        font: {
          fontColor: fontColor,
          fontSize: frameRect.height * fontRatio,
          fontWeight: fontWeight,
        },
        content: item.content
      });
      
      switch (item.align) {
        case 'left-top':
          x = padding
          y = (frameRect.height - colRect.leftContentHeight) / 2
          break
        case 'left-bottom':
          x = padding
          y = leftTopItem.height + marginBottom + (frameRect.height - colRect.leftContentHeight) / 2
          break
        case 'right-top':
          textAlign = rightTopTextWidth > rightBottomTextWidth ? 'right' : 'left'
          x = rightTopTextWidth > rightBottomTextWidth ? -padding : frameRect.width - rightBottomTextWidth - padding
          y = (frameRect.height - colRect.rightContentHeight) / 2
          break
        case 'right-bottom':
          textAlign = rightTopTextWidth > rightBottomTextWidth ? 'left' : 'right'
          x = rightTopTextWidth > rightBottomTextWidth ? frameRect.width - rightTopTextWidth - padding : -padding
          y = rightTopItem.height + marginBottom + (frameRect.height - colRect.rightContentHeight) / 2
          break
        case 'offset-left-top':
          x = padding + 2 * lineXMargin + logoWidth
          y = (frameRect.height - colRect.offsetLeftContentHeight) / 2
          break
        case 'offset-left-bottom':
          x = padding + 2 * lineXMargin + logoWidth
          y = offsetLeftTopItem.height + marginBottom + (frameRect.height - colRect.offsetLeftContentHeight) / 2
          break
        case 'center-middle':
          x = (frameRect.width - textWidth) / 2
          y = logoHeight + marginBottom + (frameRect.height - colRect.centerMiddleContentHeight) / 2
          break
        case 'center-bottom':
          x = (frameRect.width - textWidth) / 2
          y = logoHeight + marginBottom + (centerMiddleItem.height ? (centerMiddleItem.height + marginBottom) : 0) + (frameRect.height - colRect.centerMiddleContentHeight) / 2
          break
      }
      drawText(context, {
        textAlign,
        font: {
          fontColor,
          fontWeight,
          fontSize: frameRect.height * fontRatio,
        },
        position: {x, y},
        content: item.content
      })
    } else {
      // 不同位置的logo绘制
      // 绘制logo与分割线
      const isOffsetRightMiddle = aligns.includes('offset-right-middle')
      if (isOffsetRightMiddle || aligns.includes('left-middle')) {
        const rightTextMaxWidth = Math.max(rightTopTextWidth, rightBottomTextWidth)
        const lineX = isOffsetRightMiddle ? 
          (Math.ceil(frameRect.width - rightTextMaxWidth - padding) - lineXMargin)
          :
          (padding + lineXMargin + logoWidth)
        // 绘制logo
        drawImage(context, {
          img: logoImg,
          width: logoWidth,
          height: logoHeight,
          x: isOffsetRightMiddle ? (lineX - lineXMargin - logoWidth) :  padding,
          y: (frameRect.height - logoHeight) / 2,
        })
        
        // logo左边或右边分割线
        drawLine(context, {
          x: lineX - lineWidth,
          y: (frameRect.height - lineHeight) / 2,
          width: lineWidth,
          height: lineHeight,
        })
      } else {
        // 水平垂直居中排版的logo
        drawImage(context, {
          img: logoImg,
          width: logoWidth,
          height: logoHeight,
          x: (frameRect.width - logoWidth) / 2,
          y: (frameRect.height - colRect.centerMiddleContentHeight) / 2,
        })
      }
    }
  })
}

// 设置相框背景色
const setFrameBgColor = () => {
  context.fillStyle = "#ffffff"
  context.fillRect(0, 0, frameRect.width, frameRect.height)
}

// 清除相框
const clearFrame = () => {
  context.clearRect(0, 0, frameRect.width, frameRect.height)
}

// 清除canvas绘图，优化内存
const clearContext = () => {
  clearFrame()
  canvas.setAttribute('width', 0)
  canvas.setAttribute('height', 0)
}


// 制作相框
const makeFrame = () => {
  frameMaking.value = true
  saveFileData = null
  percentage.value = 0
  // 照片尺寸
  photoSize = {
    width: photo.naturalWidth,
    height: photo.naturalHeight,
  }

  frameRect.width = photoSize.width * dpr
  frameRect.height = frameRect.width / frameProportion.value

  canvas.setAttribute('width', frameRect.width)
  canvas.setAttribute('height', frameRect.height)

  clearFrame()
  setFrameBgColor()
  
  // 绘制相框
  drawFrame(context)
  // 转blob
  canvas.toBlob(blob => {
    frameBlob = blob
    clearContext()
    // framePreviewSrc.value = window.URL.createObjectURL(blob)
    src.value = window.URL.createObjectURL(props.drawData.file)
    frameMaking.value = false
    $emit('success')
  }, 'image/jpeg', 1)
}



onMounted(() => {
  const drawData = props.drawData
  const {file } = drawData
  saveFileName = file.name.replace(/(\.(jpg|jpeg))$/ig, '_frame$1')
  photo.setAttribute('src', window.URL.createObjectURL(file))
  photo.onload = () => {
    logoImg.setAttribute('src', currentFrameConfigs.value.find(item => item.prop === 'logo').content)
    // 绘制水印相框
    logoImg.onload = () => {
      logoSrc.value = logoImg.getAttribute('src')
      makeFrame()
    }
  }
})

onBeforeUnmount(() => {
  window.URL.revokeObjectURL(src.value)
  // window.URL.revokeObjectURL(framePreviewSrc.value)
  photo.onload = null
  logoImg.onload = null
  logoImg = null
  context = null
  canvas = null
  frameRect = null
  photoSize = null
  saveFileData = null
  photo = null
  frameBlob = null
})

const photoLoad = () => {
  loaded.value = true
}

// 保存文件
const save = () => {
  remote.dialog.showSaveDialog({
    title: '保存照片',
    defaultPath: saveFileName,
  }).then(res => {
    if (!res.filePath) {
      return
    }
    fs.writeFileSync(res.filePath, saveFileData)
    // 打开文件夹并聚焦文件
    remote.shell.showItemInFolder(res.filePath)
    // 打开文件
    // remote.shell.openPath(res.filePath)
  }).catch(() => {

  })
}



const toExport = () => {
  if (saveFileData) {
    save()
    return
  }
  progressShow.value = true
  // 照片与相框合并生成保存文件数据
  const maxHeight = photoSize.height + Math.floor(frameRect.height / dpr)
  props.drawData.file.arrayBuffer().then(photoFileData => {
    percentage.value += 25
    let photoBuf = Buffer.from(photoFileData)
    // rotate() => 自动矫正照片exif角度
    sharp(photoBuf).rotate().png().toBuffer().then(goodPhotoBuf => {
      percentage.value += 30
      frameBlob.arrayBuffer().then((fileData) => {
        percentage.value += 15
        let buf = Buffer.from(fileData)
        sharp(buf).resize({
          width: photoSize.width,
          height: Math.floor(frameRect.height / dpr),
        }).png().toBuffer().then(frameBuf => {
          percentage.value += 15
          sharp({ create: {
            height: maxHeight, width: photoSize.width, channels: 4, background: '#ffffff'
          }}).png().composite([
            {
              input: goodPhotoBuf,
              top: 0,
              left: 0,
            },
            {
              input: frameBuf,
              top: photoSize.height,
              left: 0,
            },
          ]).jpeg({ quality: 100 }).toBuffer().then(resultBuf => {
            percentage.value = 100
            saveFileData = resultBuf
            photoBuf = null
            buf = null
            setTimeout(() => {
              progressShow.value = false
              save()
            }, 1000)
          })
        })
      })
    })
  })
  
}

const onMenuClick = (name) => {
  $emit('menu-click', {
    name,
  })
  if (name === 'edit-frame') {
    editEnable.value = true
  }
  if (name === 'select-frame') {
    frameDrawerShow.value = true
  }
}

const frameMenuClick = (e) => {
  const {name} = e
  // 取消
  if (name === 'cancel') {
    editEnable.value = false
  }
  // 确认
  if (name === 'confirm') {
    currentFrameConfigs.value = e.configs
    logoImg.setAttribute('src', e.configs.find(item => item.prop === 'logo').content)
    editEnable.value = false
  }
  $emit('menu-click', {
    name,
  })
}

const frameSelectSuccess =(e) => {
  currentFrameConfigs.value = e.frameConfigs
  frameProportion.value = e.frameProportion
  frameDrawerShow.value = false
  makeFrame()
}

// 处理element-ui自动为body元素设置className='el-popup-parent--hidden'失效的bug
const frameDrawerOpened = () => {
  document.body.classList.add('el-popup-parent--hidden')
  document.body.style='width: calc(100% - 10px);'
}
const frameDrawerClosed = () => {
  document.body.classList.remove('el-popup-parent--hidden')
  document.body.style=''
}


</script>



<style lang="less" scoped>
.draw-view {
  width: 60%;
  min-width: 1000px;
  .img-view {
    position: relative;
    // 遮盖图片: 禁止拖拽图片到电脑桌面
    &:after {
      content: "";
      position: absolute;
      left: 0;
      top: 0;
      right: 0;
      bottom: 0;
      background-color: rgba(0,0,0,0);
    }
  }

  img {
    width: 100%;
    height: auto;
    display: block;
  }

  .tool-bar {
    margin-top: 20px;
    display: flex;
    justify-content: center;
    align-items: center;
    padding: 15px;
    background-color: #222;
  }
  .export-progress-modal {
    position: fixed;
    left: 0;
    top: 0;
    right: 0;
    bottom: 0;
    z-index: 999;
    background-color: rgba(0,0,0,0.85);
    display: flex;
    justify-content: center;
    align-items: center;
  }
  .progress-main {
    width: 500px;
    :deep(.el-progress__text) {
      display: none;
    }
    .desc {
      text-align: center;
      padding: 20px;
    }
  }
}

</style>
